package ex2.grammars;

import common.exceptions.ParseException;
import ex2.Token;
import ex2.grammars.factories.BinaryOpFactory;
import ex2.grammars.factories.LiteralFactory;
import ex2.grammars.factories.TypeFactory;
import fun.grammar.Word;
import fun.parser.Tree;
import ic.ast.Node;
import ic.ast.decl.*;
import ic.ast.expr.*;
import ic.ast.stmt.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: Roi
 * Date: 11/22/13
 */
public class ProgramGrammar extends AbstractGrammar {

    private final String GRAMMAR =

            "S -> Classes_Declarations\n" +
            "Classes_Declarations -> Class_Declaration Classes_Declarations | Epsilon\n" +
            "Class_Declaration -> class CLASS_ID Class_Extends_Q { Field_Methods }\n" +
            "Class_Extends_Q -> extends CLASS_ID | Epsilon\n" +
            "Type_Field -> Type | Type_Field [ ]\n" +
            "Type -> int | boolean | string | CLASS_ID\n" +
            "Command_Ids -> Command_Id Command_Ids | Epsilon\n" +
            "Command_Id -> , ID\n" +
            "Field_Methods -> Field_Method Field_Methods | Epsilon\n" +
            "Field_Method -> Field | Method\n" +
            "Field -> Type_Field ID Command_Ids ;\n" +
            "Sequence -> Formal Commands | Epsilon\n" +
            "Commands -> Command Commands | Epsilon\n" +
            "Command -> , Formal\n" +
            "Formal -> Type_Field ID\n" +
            "Method -> Type_Void ID ( Sequence ) { Statements } | static Type_Void ID ( Sequence ) { Statements }\n" +
            "Type_Void -> Type_Field | void\n" +
            "Statements -> Statement Statements | Epsilon\n" +
            "Statement -> Normal_Statement_If \n" +
            "Normal_Statement -> Location = Expression ; | Call ; | return Expression ; | return ; | while ( Expression ) Statement | break ; | continue ; | { Statements } "
            + "| Type_Field ID ; | Type_Field ID = Expression ;\n" +
            "Expression ->  Logic_Or\n" +
            "Type_New -> new Type_Field [ Expression ] | new Type_Field [ Expression ] . length\n" +
            "Parameter_Dot -> Parameter_Dot . length | Parameter_Dot . Normal_Call | Parameter_Dot . ID | Parameter_Dot [ Expression ] | CLASS_ID . Normal_Call | ( Expression ) | Normal_Expression\n" +
            "Normal_Expression -> ID | Normal_Call | this | new CLASS_ID ( ) | Literal\n" +
            "Logic_Or -> Logic_Or || Logic_And | Logic_And\n" +
            "Logic_And -> Logic_And && Equation | Equation\n" +
            "Equation -> Operations == Operations | Operations != Operations | Operations\n" +
            "Operations -> Plus_Minus < Plus_Minus | Plus_Minus <= Plus_Minus | Plus_Minus > Plus_Minus | Plus_Minus >= Plus_Minus | Plus_Minus\n" +
            "Normal_Call -> ID ( ) | ID ( Expression Command_Expressions )\n" +
            "Command_Expressions -> Command_Expression Command_Expressions | Epsilon\n" +
            "Command_Expression -> , Expression \n" +
            "Normal_Statement_If -> if ( Expression ) Normal_Statement_If | Normal_Statement_If_Else\n" +
            "Normal_Statement_If_Else -> if ( Expression ) Normal_Statement_If_Else else Normal_Statement_If | Normal_Statement\n" +
            "Plus_Minus -> Plus_Minus + Div_Mul_Mod | Plus_Minus - Div_Mul_Mod | Div_Mul_Mod\n" +
            "Div_Mul_Mod -> Div_Mul_Mod * Un_Operation | Div_Mul_Mod / Un_Operation | Div_Mul_Mod % Un_Operation | Un_Operation\n" +
            "Un_Operation -> - Parameter_Dot | ! Parameter_Dot | Parameter_Dot | Type_New\n" +
            "Call -> CLASS_ID . Normal_Call | Normal_Call | Expression . Normal_Call\n" +
            "Location -> ID | Expression . ID | Expression [ Expression ]\n" +
            "Literal -> INTEGER | STRING | true | false | null\n" +
            "Epsilon ->  \n" ;

    private List<DeclClass> classes = new ArrayList<DeclClass>();
    private boolean intCheckFlag = true;

    @Override
    public String getDefinition() {
        return GRAMMAR;
    }

    @Override
    public Node constructAst(Tree parseTree) throws Exception {
        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        switch (root.tag) {
            case "S": {
                return constructAst(subtrees[0]);
            }

            case "Epsilon": {
                return null;
            }

            case "CLASS_ID": {
                Token token = (Token) root;
                return new ClassType(token.getLine(), token.getValue());
            }

            case "Classes_Declarations": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    classes.add((DeclClass) constructAst(subtrees[0]));
                    constructAst(subtrees[1]);
                    return new Program(classes);
                }
            }

            case "Class_Declaration": {
                ClassType classId = (ClassType) constructAst(subtrees[1]);
                ClassType extend = (ClassType) constructAst(subtrees[2]);

                List<DeclField> fields = new ArrayList<DeclField>();
                List<DeclMethod> methods = new ArrayList<DeclMethod>();

                getFieldsAndMethods(subtrees[4], fields, methods);

                if (extend == null)
                    return new DeclClass(classId.getLine(), classId.getClassName(), fields, methods);
                else
                    return new DeclClass(classId.getLine(), classId.getClassName(), extend.getClassName(), fields, methods);
            }

            case "Class_Extends_Q": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    Token token = (Token) subtrees[1].root;
                    return new ClassType(token.getLine(), token.getValue());
                }
            }

            default: {
                throw new Exception("Error");
            }
        }
    }

    private Node getFieldsAndMethods(Tree parseTree,
                                     List<DeclField> fields,
                                     List<DeclMethod> methods) throws Exception {
        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        switch (root.tag) {
            case "Epsilon": {
                return null;
            }

            case "void": {
                Token token = (Token) root;
                return new PrimitiveType(token.getLine(), PrimitiveType.DataType.VOID);
            }

            case "Field": {
                if (subtrees.length == 4) {
                    Type fieldsAndMethods = (Type) getFieldsAndMethods(subtrees[0], fields, methods);
                    Token token = (Token) subtrees[1].root;
                    fields.add(new DeclField(fieldsAndMethods, token.getValue()));
                    initFields(subtrees[2], fields, fieldsAndMethods);
                    return null;
                }
            }

            case "Type_Field": {
                if (subtrees.length == 1) {
                    return getFieldsAndMethods(subtrees[0], fields, methods);

                }
                if (subtrees.length == 3) {
                    Type fieldsAndMethods = (Type) getFieldsAndMethods(subtrees[0], fields, methods);
                    for (int i = 1; i < subtrees.length + 1; i++) {
                        if (i % 2 == 0) {
                            fieldsAndMethods.incrementDimension();
                        }
                    }

                    return fieldsAndMethods;
                }
            }

            case "Field_Methods": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {

                    getFieldsAndMethods(subtrees[0], fields, methods);
                    getFieldsAndMethods(subtrees[1], fields, methods);

                }
            }

            case "Field_Method": {
                switch (subtrees[0].root.tag) {
                    case "Field":
                        getFieldsAndMethods(subtrees[0], fields, methods);
                        return null;

                    case "Method":
                        getFieldsAndMethods(subtrees[0], fields, methods);

                    default:
                        return null;
                }
            }

            case "Type": {
                if (subtrees.length == 1) {
                    Token token = (Token) subtrees[0].root;
                    return TypeFactory.create(token);
                }
            }

            case "Type_Void": {
                return getFieldsAndMethods(subtrees[0], fields, methods);
            }

            case "Method": {
                List<Parameter> Formals = new ArrayList<Parameter>();
                List<Statement> statements = new ArrayList<Statement>();

                if (subtrees.length == 8) {
                    Type fieldsAndMethods = (Type) getFieldsAndMethods(subtrees[0], fields, methods);
                    Token token = (Token) subtrees[1].root;

                    getFormalsAndStatements(subtrees[3], fields, methods, Formals, statements);
                    getFormalsAndStatements(subtrees[6], fields, methods, Formals, statements);

                    DeclMethod dm = new DeclVirtualMethod(fieldsAndMethods, token.getValue(), Formals, statements);
                    methods.add(dm);
                    return dm;
                }

                if (subtrees.length == 9) {
                    Type fieldsAndMethods = (Type) getFieldsAndMethods(subtrees[1], fields, methods);
                    Token token = (Token) subtrees[2].root;

                    getFormalsAndStatements(subtrees[4], fields, methods, Formals, statements);

                    getFormalsAndStatements(subtrees[7], fields, methods, Formals, statements);
                    DeclMethod dm = new DeclStaticMethod(fieldsAndMethods, token.getValue(), Formals, statements);
                    methods.add(dm);
                    return dm;

                }
            }

            default: {
                throw new Exception("Error");
            }
        }
    }

    private Node getFormalsAndStatements(Tree parseTree, List<DeclField> fields,
                                         List<DeclMethod> methods,
                                         List<Parameter> formals,
                                         List<Statement> statements) throws Exception {
        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        switch (root.tag) {
            case "Statement": {
                return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
            }

            case "Statements": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    Statement statement = (Statement) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    statements.add(statement);
                    getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                    return statement;
                }
            }

            case "Sequence": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                    return null;
                }
            }

            case "Formal": {
                Type type = (Type) getFieldsAndMethods(subtrees[0], fields, methods);
                Token token = (Token) subtrees[1].root;
                Parameter param = new Parameter(type, token.getValue());
                formals.add(param);
                return null;
            }

            case "Commands": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                    return null;
                }
            }

            case "Command": {
                getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                return null;
            }

            case "Normal_Statement_If": {
                if (subtrees.length == 1) {
                    return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                }
                if (subtrees.length == 5) {
                    Expression expression = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    Statement statement = (Statement) getFormalsAndStatements(subtrees[4], fields, methods, formals, statements);
                    return new StmtIf(expression, statement);
                }
            }

            case "Normal_Statement_If_Else": {
                if (subtrees.length == 1) {
                    return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                }
                if (subtrees.length == 7) {
                    Expression expression = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    Statement statementIf = (Statement) getFormalsAndStatements(subtrees[4], fields, methods, formals, statements);
                    Statement statementElse = (Statement) getFormalsAndStatements(subtrees[6], fields, methods, formals, statements);
                    return new StmtIf(expression, statementIf, statementElse);
                }
            }

            case "Equation":
            case "Operations":
            case "Plus_Minus":
            case "Div_Mul_Mod": {
                if (subtrees.length == 1) {
                    return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                }
                if (subtrees.length == 3) {
                    Expression expressionLhs = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    Expression expressionRhs = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    Token token = (Token) subtrees[1].root;
                    return BinaryOpFactory.create(token, expressionLhs, expressionRhs);
                }
            }

            case "Normal_Statement": {
                if (subtrees.length == 2) {
                    switch (subtrees[0].root.tag) {
                        case "Call": {
                            return new StmtCall((Call) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements));
                        }

                        case "break": {
                            Token token = (Token) subtrees[0].root;
                            return new StmtBreak(token.getLine());
                        }

                        case "continue": {
                            Token token = (Token) subtrees[0].root;
                            return new StmtContinue(token.getLine());
                        }

                        case "return": {
                            Token token = (Token) subtrees[0].root;
                            return new StmtReturn(token.getLine());
                        }

                        default:
                            throw new Exception("Error");

                    }
                }
                if (subtrees.length == 3) {
                    switch (subtrees[0].root.tag) {
                        case "return": {
                            Expression e3 = (Expression) getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                            Token token = (Token) subtrees[0].root;
                            return new StmtReturn(token.getLine(), e3);
                        }

                        case "{": {
                            List<Statement> statements1 = new ArrayList<Statement>();
                            getFormalsAndStatements(subtrees[1], fields, methods, formals, statements1);
                            Token token = (Token) subtrees[0].root;
                            return new StmtBlock(token.getLine(), statements1);
                        }

                        case "Type_Field": {
                            Type type = (Type) getFieldsAndMethods(subtrees[0], fields, methods);
                            Token tokenVar = (Token) subtrees[1].root;
                            return new LocalVariable(tokenVar.getLine(), type, tokenVar.getValue());
                        }
                    }
                }
                if (subtrees.length == 4) {
                    Ref ref = (Ref) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    Expression expression = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    return new StmtAssignment(ref, expression);
                }
                if (subtrees.length == 5) {
                    switch (subtrees[0].root.tag) {
                        case "while": {
                            Expression expression = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                            Statement statement = (Statement) getFormalsAndStatements(subtrees[4], fields, methods, formals, statements);
                            return new StmtWhile(expression, statement);
                        }

                        case "Type_Field": {
                            Type type = (Type) getFieldsAndMethods(subtrees[0], fields, methods);
                            Token token = (Token) subtrees[1].root;
                            Expression initValue = (Expression) getFormalsAndStatements(subtrees[3], fields, methods, formals, statements);
                            return new LocalVariable(token.getLine(), type, token.getValue(), initValue);
                        }
                    }
                }
            }

            case "Expression": {
                return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
            }

            case "Logic_And": {
                if (subtrees.length == 1) {
                    return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                }
                if (subtrees.length == 3) {
                    Expression expressionLhs = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    Expression expressionRhs = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    Token token = (Token) subtrees[1].root;
                    return new BinaryOp(token.getLine(), expressionLhs, BinaryOp.BinaryOps.LAND, expressionRhs);
                }
            }

            case "Logic_Or": {
                if (subtrees.length == 1) {
                    return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                }
                if (subtrees.length == 3) {
                    Expression expressionLhs = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    Expression expressionRhs = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    Token token = (Token) subtrees[1].root;
                    return new BinaryOp(token.getLine(), expressionLhs, BinaryOp.BinaryOps.LOR, expressionRhs);
                }
            }

            case "Un_Operation": {
                if (subtrees.length == 1) {
                    return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                }
                if (subtrees.length == 2) {
                    Token token = (Token) subtrees[0].root;
                    switch (token.getValue()) {
                        case "!": {
                            Expression expression = (Expression) getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                            return new UnaryOp(token.getLine(), UnaryOp.UnaryOps.LNEG, expression);
                        }

                        case "-": {
                            if (!checkNegativeIntBounds(subtrees[1], 0)) {
                                intCheckFlag = false;
                            }

                            Expression expression = (Expression) getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                            intCheckFlag = true;
                            if (expression instanceof Literal) {
                                Literal literal = (Literal) expression;
                                if (literal.getType() == PrimitiveType.DataType.INT) {
                                    return new Literal(literal.getLine(), PrimitiveType.DataType.INT, "-" + literal.getValue());
                                }
                            }

                            return new UnaryOp(token.getLine(), UnaryOp.UnaryOps.UMINUS, expression);
                        }
                    }
                }
            }

            case "Type_New": {
                if (subtrees.length == 5) {
                    Type type = (Type) getFieldsAndMethods(subtrees[1], fields, methods);
                    Expression expression = (Expression) getFormalsAndStatements(subtrees[3], fields, methods, formals, statements);
                    return new NewArray(type, expression);
                }
                if (subtrees.length == 7) {
                    Type type = (Type) getFieldsAndMethods(subtrees[1], fields, methods);
                    Expression expression = (Expression) getFormalsAndStatements(subtrees[3], fields, methods, formals, statements);
                    NewArray array = new NewArray(type, expression);
                    Token token = (Token) subtrees[5].root;
                    return new Length(token.getLine(), array);
                }
            }

            case "Parameter_Dot": {
                if (subtrees.length == 1) {
                    return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                }

                if (subtrees.length == 3) {
                    switch (subtrees[2].root.tag) {
                        case ")": {
                            return getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                        }

                        case "length": {
                            Token token = (Token) subtrees[2].root;
                            Expression expression = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                            return new Length(token.getLine(), expression);
                        }

                        case "ID": {
                            Expression expression = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                            List<Expression> arguments = new ArrayList<Expression>();
                            Token token = getSimpleCall(subtrees[2], fields, methods, formals, statements, arguments);
                            return new VirtualCall(token.getLine(), expression, token.getValue(), arguments);
                        }

                        case "Normal_Call": {
                            if (subtrees[0].root.tag.equals("Parameter_Dot")) {
                                Expression expression = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                                List<Expression> arguments = new ArrayList<Expression>();
                                Token token = getSimpleCall(subtrees[2], fields, methods, formals, statements, arguments);
                                return new VirtualCall(token.getLine(), expression, token.getValue(), arguments);
                            }
                            if (subtrees[0].root.tag.equals("CLASS_ID")) {
                                ClassType classType = (ClassType) constructAst(subtrees[0]);
                                List<Expression> arguments = new ArrayList<Expression>();
                                Token token = getSimpleCall(subtrees[2], fields, methods, formals, statements, arguments);
                                return new StaticCall(classType.getLine(), classType.getClassName(), token.getValue(), arguments);
                            }
                        }
                    }
                }

                if (subtrees.length == 4) {
                    Expression array = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    Expression index = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    return new RefArrayElement(array, index);
                }
            }

            case "Normal_Expression": {
                if (subtrees.length == 1) {
                    switch (subtrees[0].root.tag) {
                        case "Literal": {
                            return getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                        }

                        case "this": {
                            Token token = (Token) subtrees[0].root;
                            return new This(token.getLine());
                        }

                        case "ID": {
                            Token token = (Token) subtrees[0].root;
                            return new RefVariable(token.getLine(), token.getValue());
                        }

                        case "Normal_Call": {
                            List<Expression> arguments = new ArrayList<Expression>();
                            Token token = getSimpleCall(subtrees[0], fields, methods, formals, statements, arguments);
                            return new VirtualCall(token.getLine(), token.getValue(), arguments);
                        }
                    }
                }

                if (subtrees.length == 4) {
                    ClassType classType = (ClassType) constructAst(subtrees[1]);
                    return new NewInstance(classType.getLine(), classType.getClassName());
                }
            }

            case "Literal": {
                Token token = (Token) subtrees[0].root;
                if(token.tag.equals("INTEGER")) {
                    if (intCheckFlag) {
                        if (!isInteger(token.getValue())) {
                            throw new ParseException(token.getLine() + ":" + token.getColumn() + " : syntax error; numeric literal out of range: " + token.getValue());
                        }
                    }
                    return new Literal(token.getLine(), PrimitiveType.DataType.INT, token.getValue());
                }
                else {
                    return LiteralFactory.create(token);
                }
            }

            case "Location": {
                if (subtrees.length == 1) {
                    Token token = (Token) subtrees[0].root;
                    return new RefVariable(token.getLine(), token.getValue());
                }
                if (subtrees.length == 3) {
                    Token token = (Token) subtrees[2].root;
                    Expression object = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    return new RefField(token.getLine(), object, token.getValue());
                }
                if (subtrees.length == 4) {
                    Expression array = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                    Expression index = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    return new RefArrayElement(array, index);
                }
            }

            case "Call": {
                if (subtrees.length == 1) {
                    List<Expression> arguments = new ArrayList<Expression>();
                    Token token = getSimpleCall(subtrees[0], fields, methods, formals, statements, arguments);
                    return new VirtualCall(token.getLine(), token.getValue(), arguments);
                }
                if (subtrees.length == 3) {
                    switch (subtrees[0].root.tag) {
                        case "Expression": {
                            Expression expression = (Expression) getFormalsAndStatements(subtrees[0], fields, methods, formals, statements);
                            List<Expression> arguments = new ArrayList<Expression>();
                            Token token = getSimpleCall(subtrees[2], fields, methods, formals, statements, arguments);
                            return new VirtualCall(token.getLine(), expression, token.getValue(), arguments);
                        }

                        case "CLASS_ID": {
                            ClassType classType = (ClassType) constructAst(subtrees[0]);
                            List<Expression> arguments = new ArrayList<Expression>();
                            Token token = getSimpleCall(subtrees[2], fields, methods, formals, statements, arguments);
                            return new StaticCall(classType.getLine(), classType.getClassName(), token.getValue(), arguments);
                        }
                    }
                }
            }

            default: {
                return null;
            }
        }
    }

    private Token getSimpleCall(Tree parseTree, List<DeclField> fields,
                                List<DeclMethod> methods,
                                List<Parameter> formals,
                                List<Statement> statements,
                                List<Expression> arguments) throws Exception {
        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        switch (root.tag) {
            case "Command_Expressions": {
                if (subtrees.length == 1) {
                    return null;
                }
                if (subtrees.length == 2) {
                    getSimpleCall(subtrees[0], fields, methods, formals, statements, arguments);
                    getSimpleCall(subtrees[1], fields, methods, formals, statements, arguments);
                    return null;
                }
            }

            case "Command_Expression": {
                if (subtrees.length == 2) {
                    Expression expression = (Expression) getFormalsAndStatements(subtrees[1], fields, methods, formals, statements);
                    arguments.add(expression);
                    return null;
                }
            }

            case "Normal_Call": {

                if (subtrees.length == 3) {
                    return (Token) subtrees[0].root;
                }

                if (subtrees.length == 5) {
                    Expression expression = (Expression) getFormalsAndStatements(subtrees[2], fields, methods, formals, statements);
                    arguments.add(expression);
                    getSimpleCall(subtrees[3], fields, methods, formals, statements, arguments);
                    return (Token) subtrees[0].root;
                }
            }

            default:
                return null;
        }
    }

    private Node initFields(Tree parseTree,
                            List<DeclField> fields,
                            Type type) {

        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        switch (root.tag) {
            case "Command_Id": {

                if (subtrees.length == 2) {
                    Token token = (Token) subtrees[1].root;
                    DeclField declField = new DeclField(type, token.getValue());
                    fields.add(declField);
                }
            }

            case "Command_Ids": {
                if (subtrees.length == 1) {
                    return null;
                }

                if (subtrees.length == 2) {
                    initFields(subtrees[0], fields, type);
                    initFields(subtrees[1], fields, type);
                    return null;
                }
            }

            default:
                return null;
        }
    }

    private boolean checkNegativeIntBounds(Tree parseTree, int i) throws Exception {
        Word root = parseTree.root;
        Tree[] subtrees = parseTree.subtrees.toArray(new Tree[0]);

        if (i == 3) {
            if (root.tag.equals("INTEGER")) {
                Token t = (Token) root;
                if (!isInteger("-" + t.getValue())) {
                    throw new ParseException(t.getLine() + ":" + t.getColumn() + " : syntax error; numeric literal out of range: -" + t.getValue());
                }
            }
        }
        if (i == 3) {
            return false;
        }

        checkNegativeIntBounds(subtrees[0], ++i);
        return false;
    }

    private boolean isInteger(String value) {
        try {
            Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return false;
        }

        return true;
    }
}
